#include <string.h>
#include <stdio.h>
#include "net.h"
#include "arp.h"
#include "ethernet.h"
/**
 * @brief 初始的arp包
 * 
 */
static const arp_pkt_t arp_init_pkt = {
    .hw_type16 = swap16(ARP_HW_ETHER),
    .pro_type16 = swap16(NET_PROTOCOL_IP),
    .hw_len = NET_MAC_LEN,
    .pro_len = NET_IP_LEN,
    .sender_ip = NET_IF_IP,
    .sender_mac = NET_IF_MAC,
    .target_mac = {0}};




static const uint8_t BOARD_CAST_MAC[NET_MAC_LEN]={0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
static const uint8_t LOCAL_IP[NET_IP_LEN]=NET_IF_IP;

/**
 * @brief arp地址转换表，<ip,mac>的容器
 * 
 */
map_t arp_table;

/**
 * @brief arp buffer，<ip,buf_t>的容器
 * 
 */
map_t arp_buf;

/**
 * @brief 打印一条arp表项
 * 
 * @param ip 表项的ip地址
 * @param mac 表项的mac地址
 * @param timestamp 表项的更新时间
 */
void arp_entry_print(void *ip, void *mac, time_t *timestamp)
{
    printf("%s | %s | %s\n", iptos(ip), mactos(mac), timetos(*timestamp));
}

/**
 * @brief 打印整个arp表
 * 
 */
void arp_print()
{
    printf("===ARP TABLE BEGIN===\n");
    map_foreach(&arp_table, arp_entry_print);
    printf("===ARP TABLE  END ===\n");
}

/**
 * @brief 发送一个arp请求
 * 
 * @param target_ip 想要知道的目标的ip地址
 */
void arp_req(uint8_t *target_ip)
{
    // 修改错误点2
    // 18B的padding
    buf_init(&txbuf,sizeof(arp_pkt_t)+18);
    // 错误点3:此处强制清零一下，不然padding的内容可能会不一样造成影响（在调试icmp时发现的问题）
    memset(txbuf.data,0,sizeof(arp_pkt_t)+18);
    // 错误点2：报文的头长度
    // buf_add_header(&txbuf,8);
    arp_pkt_t *arp_send = (arp_pkt_t *)txbuf.data;
    *arp_send = arp_init_pkt;
    arp_send->opcode16=swap16(ARP_REQUEST);
    memcpy(arp_send->target_ip,target_ip,sizeof(uint8_t)*NET_IP_LEN);
    ethernet_out(&txbuf,BOARD_CAST_MAC,NET_PROTOCOL_ARP);
}

/**
 * @brief 发送一个arp响应
 * 
 * @param target_ip 目标ip地址
 * @param target_mac 目标mac地址
 */
void arp_resp(uint8_t *target_ip, uint8_t *target_mac)
{

    buf_init(&txbuf,sizeof(arp_pkt_t)+18);
    // 错误点3:此处强制清零一下，不然padding的内容可能会不一样造成影响（在调试icmp时发现的问题）
    memset(txbuf.data,0,sizeof(arp_pkt_t)+18);
    arp_pkt_t *arp_send = (arp_pkt_t *)txbuf.data;
    *arp_send = arp_init_pkt;
    arp_send->opcode16=swap16(ARP_REPLY);
    memcpy(arp_send->target_ip,target_ip,sizeof(uint8_t)*NET_IP_LEN);
    memcpy(arp_send->target_mac,target_mac,sizeof(uint8_t)*NET_MAC_LEN);
    ethernet_out(&txbuf,target_mac,NET_PROTOCOL_ARP);
}

/**
 * @brief 处理一个收到的数据包
 * 
 * @param buf 要处理的数据包
 * (注意一下，这里的mac地址是下层传上来的，也就是eth层的源mac地址)
 * @param src_mac 源mac地址 
 */
void arp_in(buf_t *buf, uint8_t *src_mac)
{
    if (buf->len<sizeof(arp_pkt_t))
    {
        return;
    }
    // 报头检查
    // 在取出arp头的时候应该直接取就好，eth层的头应该已经在eth中去掉了
    arp_pkt_t* arp =(arp_pkt_t*) buf->data;
    buf_remove_header(buf,sizeof(arp_pkt_t));
    // 哪一些需要swap呢
    // 错误点1：没有交换(网络上传输都是大端，到本地是小端，凡是传输数据与本地数据比较都要swap)
    if (arp->hw_type16!=swap16(ARP_HW_ETHER) ||arp->pro_type16!=swap16(NET_PROTOCOL_IP)
    || arp->hw_len!=NET_MAC_LEN || arp->pro_len != NET_IP_LEN)
    {
        return;
    }
    // 区分好src_mac和arp->sender_mac
    map_set(&arp_table,arp->sender_ip,arp->sender_mac);
    buf_t* bufp =(buf_t*) map_get(&arp_buf,arp->sender_ip);
    if (bufp!=NULL)
    {
        ethernet_out(bufp,arp->sender_mac,NET_PROTOCOL_IP);
        map_delete(&arp_buf,arp->sender_ip);
        // 错误点1：没有交换
    }else if(arp->opcode16==swap16(ARP_REQUEST) && !memcmp(arp->target_ip,LOCAL_IP,NET_IP_LEN)){
        arp_resp(arp->sender_ip,arp->sender_mac);
    }
    
    
    
}

/**
 * @brief 处理一个要发送的数据包
 * 
 * @param buf 要处理的数据包
 * @param ip 目标ip地址
 * @param protocol 上层协议
 */
void arp_out(buf_t *buf, uint8_t *ip)
{
    uint8_t* mac =(uint8_t*)map_get(&arp_table,ip);
    if (mac!=NULL)
    {
        // 这里选择什么协议好呢
        ethernet_out(buf,mac,NET_PROTOCOL_IP);
    }else{
        buf_t* bufp =(buf_t*) map_get(&arp_buf,ip);
        if (bufp==NULL)
        {
            map_set(&arp_buf,ip,buf);
            arp_req(ip);
        }
    }
    
}

/**
 * @brief 初始化arp协议
 * 
 */
void arp_init()
{
    // max_size填0时初始化为255
    map_init(&arp_table, NET_IP_LEN, NET_MAC_LEN, 0, ARP_TIMEOUT_SEC, NULL);
    map_init(&arp_buf, NET_IP_LEN, sizeof(buf_t), 0, ARP_MIN_INTERVAL, buf_copy);
    net_add_protocol(NET_PROTOCOL_ARP, arp_in);
    arp_req(net_if_ip);
}